/**
 * 手写call、apply、bind
 */

Function.prototype.myCall = function (context) {
  context = context ? Object(context) : window;

  /*context下添加一个属性指向this，this就是调用call方法的函数，
  等同于context下添加一个方法，context调用他时this也就指向了context*/
  context.fn = this;
  const args = [...arguments].slice(1);
  //通过context调用此方法得到结果result 调用完毕后delete删除此属性
  const result = context.fn(...args);
  delete context.fn;
  return result;
};

Function.prototype.myApply = function (context) {
  context = context ? Object(context) : window;
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  context.fn = this;
  let result;
  if (arguments[1]) {
    let args = arguments[1];
    result = context.fn(...args);
  } else {
    result = context.fn();
  }
  delete context.fn;
  return result;
};

Function.prototype.myBind = function (context) {
  context = context ? Object(context) : window;
  let _this = this;
  let args = [...arguments].slice(1);
  var fn = function() {
    if(this instanceof fn){
      this.fn=_this
      this.fn(...args,...arguments)
      delete this.fn
    }else{
      context.fn=this
      context.fn(...args,...arguments)
      delete context.fn
    }

    
  }
  fn.prototype=Object.create(this.prototype)
  return fn
};

Function.prototype.myBind = function (context, ...args) {
  context = context ? context : window;
  // 创造唯一的key值  作为我们构造的context内部方法名

  context[fn] = this;
  let _this = this;
  //  bind情况要复杂一点
  const result = function (...innerArgs) {
    // 第一种情况 :若是将 bind 绑定之后的函数当作构造函数，通过 new 操作符使用，则不绑定传入的 this，而是将 this 指向实例化出来的对象
    // 此时由于new操作符作用  this指向result实例对象  而result又继承自传入的_this 根据原型链知识可得出以下结论
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时this指向指向result的实例  这时候不需要改变this指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
      delete this[fn];
    } else {
      // 如果只是作为普通函数调用  那就很简单了 直接改变this指向为传入的context
      context[fn](...[...args, ...innerArgs]);
      delete context[fn];
    }
  };
  // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
  // 实现继承的方式: 使用Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

function test() {
  console.log(this.name);
}

let you = {
  name: "you",
};

let me = {
  name: "me",
  test: function () {
    console.log(this.name);
  },
};

const aa = me.test.mybind(you);
aa();

function mybind(context) {
  context = context ? Object(context) : window;
  //是否是一个函数调用此bind函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  //返回一个绑定this的函数，这里我们需要保存this
  const _this = this;
  //bind函数传来的参数
  const args = [...arguments].slice(1);
  //返回一个函数
  return function F() {
    //因为返回一个函数，我们可以new F()判断是否为new调用 arguments为新函数传来的参数
    if (this instanceof F) {
      return new _this(...args, ...arguments);
    }
    return _this.apply(context, args.concat(...arguments));
  };
}
